/* ---------------------------------- lcd tyy start ---------- */

#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))

#define TERMIOS_FLUSH   1
#define TERMIOS_WAIT    2
#define TERMIOS_TERMIO  4
#define TERMIOS_OLD     8

static int phc_tty_open(struct tty_struct *tty, struct file *file)
{
	struct phc_vc_data *phc_vc = tty->driver_data;
	
	PDEBUG_TTY("phc_tty_open %d %d \n", tty->index, current_tty);
	
	if (!phc_vc) {
		/* first time accessing this device */
		phc_vc = kmalloc(sizeof(*phc_vc), GFP_KERNEL);
		if (!phc_vc) {
			PERR("cannot allocate memory phc_vc\n");
			goto fail_phc_vc_kmalloc;
		}
		reset_terminal(phc_vc, 0);
		phc_vc->vc_screenbuf_size = disp_cols*disp_rows;
		phc_vc->vc_screenbuf = kcalloc(phc_vc->vc_screenbuf_size, sizeof(*phc_vc->vc_screenbuf), GFP_KERNEL);
		if (!phc_vc->vc_screenbuf) {
			PERR("cannot allocate memory phc_vc->vc_screenbuf\n");
			goto fail_phc_vc_state_kcalloc;
		}
		memset(phc_vc->vc_screenbuf, charmap[' '], phc_vc->vc_screenbuf_size*sizeof(*phc_vc->vc_screenbuf));
		if (tty->index == 0) {
			clear_all_displays();
			current_tty = tty;
		}
		
		mutex_init(&phc_vc->mutex);
		phc_vc->open_count = 0;
		
		tty->winsize.ws_row = disp_rows;
		tty->winsize.ws_col = disp_cols;
	}

	
	mutex_lock(&phc_vc->mutex);

	/* save our structure within the tty structure */
	tty->driver_data = phc_vc;
	phc_vc->tty = tty;

	++phc_vc->open_count;
	if (phc_vc->open_count == 1) {
		/* this is the first time this port is opened */
		/* do any hardware initialization needed here */
	}

	mutex_unlock(&phc_vc->mutex);
	return 0;
	
fail_phc_vc_state_kcalloc:
	kfree(phc_vc);
fail_phc_vc_kmalloc:
	return -ENOMEM;
}

static void phc_tty_close(struct tty_struct *tty, struct file *file)
{
	struct phc_vc_data *phc_vc = tty->driver_data;
	PDEBUG_TTY("phc_tty_close\n");
	if (!phc_vc) return;
	
	mutex_lock(&phc_vc->mutex);
	
	/* port was never opened */
	if (!phc_vc->open_count) 
		goto exit;
	
	--phc_vc->open_count;
	if (phc_vc->open_count)
		goto exit;
	if (tty->index == 0)
		current_tty = NULL;
	kfree(phc_vc->vc_screenbuf);
	kfree(phc_vc);
	
	/* The port is being closed by the last user. */
	/* Do any hardware specific stuff here */
	
exit:
	mutex_unlock(&phc_vc->mutex);
}

static int phc_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
	struct phc_vc_data *vc = tty->driver_data;
	int i;
	if (!vc) return -ENODEV;
	
	if (in_interrupt())
		return count;

	mutex_lock(&vc->mutex);
	
	if (!vc->open_count) {
		mutex_unlock(&vc->mutex);
		return -EINVAL;
	}
	PDEBUG_TTY("on %d writing %d - ", tty->index, count);
	for (i=0; i < count; i++)
		PDEBUG_TTY("%c", buf[i]);
	PDEBUG_TTY("\n");
	
	if (current_tty == tty) {
		__do_con_write(tty, buf, count);
	}
	
	mutex_unlock(&vc->mutex);
	return count;
}

static int phc_put_char(struct tty_struct *tty, const unsigned char ch)
{
	struct phc_vc_data *vc = tty->driver_data;
	if (!vc) return -ENODEV;
	
	if (in_interrupt())
		return 1;

	mutex_lock(&vc->mutex);
	
	if (!vc->open_count) {
		mutex_unlock(&vc->mutex);
		return -EINVAL;
	}
	PDEBUG_TTY("on %d writing one - %x %c \n", tty->index, ch, ch);
	
	if (current_tty == tty) {
		__do_con_write(tty, &ch, 1);
	}
	
	mutex_unlock(&vc->mutex);
	return 1;
}

static int phc_tty_write_room(struct tty_struct *tty) 
{
	struct phc_vc_data *phc_vc = tty->driver_data;
	if (!phc_vc) return -ENODEV;
	if (!phc_vc->open_count) return -EINVAL;
	PDEBUG_TTY("phc_tty_write_room \n");
	/* we have much free space */
	return 32768;
}

static int phc_tty_chars_in_buffer(struct tty_struct *tty)
{
	PDEBUG_TTY("phc_tty_chars_in_buffer \n");
	return 0;
}

static void change_termios(struct tty_struct *tty, struct ktermios *new_termios)
{
        struct ktermios old_termios;
        struct tty_ldisc *ld;
        unsigned long flags;

        /*
         *      Perform the actual termios internal changes under lock.
         */


        /* FIXME: we need to decide on some locking/ordering semantics
           for the set_termios notification eventually */
        mutex_lock(&tty->termios_mutex);
        old_termios = *tty->termios;
        *tty->termios = *new_termios;

        /* See if packet mode change of state. */
        if (tty->link && tty->link->packet) {
                int extproc = (old_termios.c_lflag & EXTPROC) |
                                (tty->termios->c_lflag & EXTPROC);
                int old_flow = ((old_termios.c_iflag & IXON) &&
                                (old_termios.c_cc[VSTOP] == '\023') &&
                                (old_termios.c_cc[VSTART] == '\021'));
                int new_flow = (I_IXON(tty) &&
                                STOP_CHAR(tty) == '\023' &&
                                START_CHAR(tty) == '\021');
                if ((old_flow != new_flow) || extproc) {
                        spin_lock_irqsave(&tty->ctrl_lock, flags);
                        if (old_flow != new_flow) {
                                tty->ctrl_status &= ~(TIOCPKT_DOSTOP | TIOCPKT_NOSTOP);
                                if (new_flow)
                                        tty->ctrl_status |= TIOCPKT_DOSTOP;
                                else
                                        tty->ctrl_status |= TIOCPKT_NOSTOP;
                        }
                        if (extproc)
                                tty->ctrl_status |= TIOCPKT_IOCTL;
                        spin_unlock_irqrestore(&tty->ctrl_lock, flags);
                        wake_up_interruptible(&tty->link->read_wait);
                }
        }
        if (tty->ops->set_termios)
                (*tty->ops->set_termios)(tty, &old_termios);
        else
                tty_termios_copy_hw(tty->termios, &old_termios);

        ld = tty_ldisc_ref(tty);
        if (ld != NULL) {
                if (ld->ops->set_termios)
                        (ld->ops->set_termios)(tty, &old_termios);
                tty_ldisc_deref(ld);
        }
        mutex_unlock(&tty->termios_mutex);
}

static int set_termios(struct tty_struct *tty, void __user *arg, int opt)
{
        struct ktermios tmp_termios;
        struct tty_ldisc *ld;
        int retval = tty_check_change(tty);

        if (retval)
                return retval;

        mutex_lock(&tty->termios_mutex);
        memcpy(&tmp_termios, tty->termios, sizeof(struct ktermios));
        mutex_unlock(&tty->termios_mutex);

        if (opt & TERMIOS_TERMIO) {
                if (user_termio_to_kernel_termios(&tmp_termios,
                                                (struct termio __user *)arg))
                        return -EFAULT;
        } else if (opt & TERMIOS_OLD) {
                if (user_termios_to_kernel_termios_1(&tmp_termios,
                                                (struct termios __user *)arg))
                        return -EFAULT;
        } else {
                if (user_termios_to_kernel_termios(&tmp_termios,
                                                (struct termios2 __user *)arg))
                        return -EFAULT;
        }
        /* If old style Bfoo values are used then load c_ispeed/c_ospeed
         * with the real speed so its unconditionally usable */
        tmp_termios.c_ispeed = tty_termios_input_baud_rate(&tmp_termios);
        tmp_termios.c_ospeed = tty_termios_baud_rate(&tmp_termios);

        ld = tty_ldisc_ref(tty);

        if (ld != NULL) {
                if ((opt & TERMIOS_FLUSH) && ld->ops->flush_buffer)
                        ld->ops->flush_buffer(tty);
                tty_ldisc_deref(ld);
        }

        if (opt & TERMIOS_WAIT) {
                tty_wait_until_sent(tty, 0);
                if (signal_pending(current))
                        return -EINTR;
        }

        change_termios(tty, &tmp_termios);

        /* FIXME: Arguably if tmp_termios == tty->termios AND the
           actual requested termios was not tmp_termios then we may
           want to return an error as no user requested change has
           succeeded */
        return 0;
}

static int phc_tty_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
{
	int ret = 0;
	void __user *p = (void __user *)arg;
	//struct ktermios kterm;
	
	PDEBUG_TTY("phc_tty_ioctl %#x %ld\n", cmd , arg);
	
	switch(cmd) {
	case TCGETS:
		if (kernel_termios_to_user_termios((struct termios2 __user *)arg, tty->termios))
			ret = -EFAULT;
		return ret; 
	case TCSETS:
		return set_termios(tty, p, TERMIOS_OLD);
	case TCSETSW:
		return set_termios(tty, p, TERMIOS_WAIT | TERMIOS_OLD);
	case TCSETSF:
		return set_termios(tty, p, TERMIOS_FLUSH | TERMIOS_WAIT | TERMIOS_OLD);
	case TCFLSH:
		return tty_perform_flush(tty, arg);
	}

	printk(" ^ ioctl unknows %d %x %ld \n", cmd, cmd, arg);
	return -ENOIOCTLCMD;
}

static void phc_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
{
	unsigned int cflag;
       
	//printk("phc_tty_set_termios\n");

	cflag = tty->termios->c_cflag;

	/* check that they really want us to change something */
	if (old_termios) {
		if ((cflag == old_termios->c_cflag) &&
			(RELEVANT_IFLAG(tty->termios->c_iflag) == 
			RELEVANT_IFLAG(old_termios->c_iflag))) {
			//printk(KERN_DEBUG " - nothing to change...\n");
			return;
		}
	}
	return;

	/* get the byte size */
	switch (cflag & CSIZE) {
		case CS5:
			//printk(KERN_DEBUG " - data bits = 5\n");
			break;
		case CS6:
			//printk(KERN_DEBUG " - data bits = 6\n");
			break;
		case CS7:
			//printk(KERN_DEBUG " - data bit phc_tty_set_termios = 7\n");
			break;
		default:
		case CS8:
			//printk(KERN_DEBUG " - data bits = 8\n");
			break;
	}
	
	/* determine the parity */
	if (cflag & PARENB) {
		if (cflag & PARODD) {
			//printk(KERN_DEBUG " - parity = odd\n");
		} else {
			//printk(KERN_DEBUG " - parity = even\n");
		}
	} else {
		//printk(KERN_DEBUG " - parity = none\n");
	}
	/* figure out the stop bits requested */
	if (cflag & CSTOPB) {
		//printk(KERN_DEBUG " - stop bits = 2\n");
	} else {
		//printk(KERN_DEBUG " - stop bits = 1\n");
	}
	
	/* figure out the hardware flow control settings */
	if (cflag & CRTSCTS) {
		printk(KERN_DEBUG " - RTS/CTS is enabled\n");
	} else {
		printk(KERN_DEBUG " - RTS/CTS is disabled\n");
	}
	
	/* determine software flow control */
	/* if we are implementing XON/XOFF, set the start and 
	* stop character in the device */
	if (I_IXOFF(tty) || I_IXON(tty)) {
		unsigned char stop_char  = STOP_CHAR(tty);
		unsigned char start_char = START_CHAR(tty);
	
		/* if we are implementing INBOUND XON/XOFF */
		if (I_IXOFF(tty))
			printk(KERN_DEBUG " - INBOUND XON/XOFF is enabled, "
				"XON = %2x, XOFF = %2x", start_char, stop_char);
		else
			printk(KERN_DEBUG" - INBOUND XON/XOFF is disabled");
	
		/* if we are implementing OUTBOUND XON/XOFF */
		if (I_IXON(tty))
			printk(KERN_DEBUG" - OUTBOUND XON/XOFF is enabled, "
				"XON = %2x, XOFF = %2x", start_char, stop_char);
		else
			printk(KERN_DEBUG" - OUTBOUND XON/XOFF is disabled");
	}
	
	/* get the baud rate wanted */
	printk(KERN_DEBUG " - baud rate = %d\n", tty_get_baud_rate(tty));
}

static int phc_tty_tiocmget(struct tty_struct *tty, struct file *file)
{
	struct phc_vc_data *vc = tty->driver_data;
	unsigned int mcr;
	PDEBUG_TTY("tiny_tiocmget\n");
        mutex_lock(&vc->mutex);
        mcr = vc->mcr;
        mutex_unlock(&vc->mutex);
	return mcr;
}

static int phc_tty_tiocmset(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear)
{
	struct phc_vc_data *vc = tty->driver_data;
	unsigned int mcr;
	PDEBUG_TTY("tiny_tiocmset\n");
	mutex_lock(&vc->mutex);
	mcr = vc->mcr = vc->mcr | set | ~clear;
        mutex_unlock(&vc->mutex);
	return mcr;
}

static struct tty_operations phc_tty_ops = {
	.open = phc_tty_open,
	.close = phc_tty_close,
	.write = phc_tty_write,
	.put_char = phc_put_char,
	.write_room = phc_tty_write_room,
	.chars_in_buffer = phc_tty_chars_in_buffer,
	//.set_termios = phc_tty_set_termios,
	//.tiocmget = phc_tty_tiocmget,
	//.tiocmset = phc_tty_tiocmset,
};

/* --------------------- lcd tyy end ---------- */
